test: property-check SessionAffinityStore TTL, eviction, and reindex contracts#594
test: property-check SessionAffinityStore TTL, eviction, and reindex contracts#594ndycode wants to merge 2 commits into
Conversation
…contracts Five fast-check properties over the real store using its injectable now parameters (1s TTL floor so sequences cross expiry often): - model-based TTL/upsert equivalence: for any remember/update/forget/ advance interleaving through whitespace-decorated key spellings, getPreferredAccountIndex and getLastResponseId match a trivial TTL map (remember preserves the continuation id, response-id writes refresh expiry and never create entries) - capacity: size() never exceeds maxEntries, and LRU eviction can never evict the entry just written - write-version conflicts: a stale version loses to a live entry on both the index and response-id channels, but may rebind once the entry expires - forgetAccount + reindexAfterRemoval mirror an account-array splice, with both return counts pinned against the model - prune removes exactly the expired entries; lazily-reaped sessions (touched while expired) correctly do not count as prunable The prune model initially missed that updateLastResponseId deletes an expired entry outright; fast-check found the 8-event counterexample and the model now mirrors the lazy reap. Companion to #574/#575/#579/#592/#593; same conventions. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
|
Warning Review limit reached
More reviews will be available in 12 minutes and 24 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…une keys Greptile P1: the model carried a responseId across expiry, but the assertion block's reads lazily reap expired entries from the store, so a remember after expiry finds no existing entry and the id is gone - the model now inherits the id only from a live entry (verified at FAST_CHECK_NUM_RUNS=1000, where the original 4-event counterexample sequence reproduces without the fix). P2s: a sixth property pins clearAll (#474 invalidation) - size drops to zero, every read goes null, and the store stays usable - and the prune property now routes remember/forget/updateLastResponseId through decorated key spellings like the model property does. https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
Summary
SessionAffinityStore, the TTL/LRU store that keeps follow-up turns on the same account during rotation and whoseclearAll/reindex semantics back the [bug] Account switching works in Codex CLI but not in the Codex app #474 pin-invalidation flow.What Changed
New
test/property/session-affinity.property.test.ts(5 properties, real store driven through its injectablenowparameters — no fake timers needed):getPreferredAccountIndexandgetLastResponseIdmatch a trivial TTL map. This pins the non-obvious upsert rules:rememberpreserves the prior continuation id, response-id writes refresh the session's TTL, andupdateLastResponseIdnever creates entries. (Eviction is excluded here via a largemaxEntriesso the model stays exact; capacity is its own property.)size()never exceedsmaxEntriesunder any write stream, and LRU eviction (oldestupdatedAt) can never evict the entry that was just written.writeVersionloses to a live entry on both the account-index and response-id channels, but the same stale version may rebind the session once the entry expires.forgetAccount(removed)+reindexAfterRemoval(removed)produce exactly the mapping of an account-array splice (equal indices dropped, higher indices decremented, lower untouched), with both methods' return counts pinned against the model.prune(now)removes exactly the expired entries and is invisible to readers. The subtle case fast-check itself surfaced: sessions touched byupdateLastResponseIdwhile expired are lazily reaped on access, so they must not count as prunable — my initial model missed that and fast-check produced the 8-event counterexample on run 48; the model now mirrors the lazy reap.No SUT bugs found; the store's documented contract held everywhere.
Validation
npm test -- test/property/session-affinity.property.test.ts test/session-affinity.test.ts test/session-affinity-entry.test.ts test/issue-474-affinity-invalidation.test.ts— 43/43 (new 5 + all existing affinity suites untouched)npm run typecheck(also via pre-commit hook)npx eslint test/property/session-affinity.property.test.ts --max-warnings=0Docs and Governance Checklist
Risk and Rollback
test/property/suites (explicit vitest imports, plainfc.assert).https://claude.ai/code/session_01XNtnkLbBiXZxfQQYLMpucB
Generated by Claude Code
note: greptile review for oc-chatgpt-multi-auth. cite files like
lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.Greptile Summary
adds
test/property/session-affinity.property.test.ts, a 6-property fast-check suite forSessionAffinityStoredriven through injectablenow— no fake timers required. it addresses theclearAllcoverage gap from the previous review round and fixes the model-divergence bug where lazy reaping during assertions causedresponseIdto drift.liveModel(notmodel.get) so the model correctly reflects lazy reaping afteradvanceevents — the regression fix from round 1.nowand stay in sync with the SUT's internal eviction semantics.size() === 0immediately after and that the store accepts new writes without state corruption.Confidence Score: 5/5
additive test-only file; no production code is touched, and all 6 properties correctly model the SUT's documented contracts.
the model-divergence fix (using
liveModelinstead ofmodel.getin therememberbranch) correctly handles lazy reaping during the assertion loop. theclearAllproperty closes the gap from the prior review round. the only open item is a narrow test-coverage gap in property 3 — the stale response-id channel test only exercises the null-baseline case, leaving the non-null preservation path untested — but no SUT bug is present.test/property/session-affinity.property.test.ts — property 3's response-id stale-overwrite sub-test could be strengthened to catch regressions where a rejected write silently clears an existing response id.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[fc.assert arbSequence] --> B{event.kind} B -->|advance| C[now += ms] B -->|remember| D[store.remember\nspell key] B -->|updateResponse| E[store.updateLastResponseId\nspell key] B -->|forget| F[store.forgetSession\nspell key] D --> G[liveModel check\npreserve responseId only\nif entry still live] E --> H{entry live\nin model?} H -->|yes| I[model: update responseId\n+ refresh expiresAt] H -->|no| J[model: no-op\nlazy reap on next access] C --> K[assertion block\nafter every event] D --> K E --> K F --> K K --> L[for each KEYS\nliveModel vs\nstore.getPreferredAccountIndex\nstore.getLastResponseId] L --> M{match?} M -->|yes| N[continue] M -->|no| O[fast-check\ncounterexample]Prompt To Fix All With AI
Reviews (2): Last reviewed commit: "test: fix remember model after lazy reap..." | Re-trigger Greptile